########################################################################
#  Pyfm - personal finance data-base
#  Copyright (C) 2007,8 Ehud Ben-Reuven
#  udi@benreuven.com
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation version 2.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
########################################################################
"""
DT - class to handle date-time
TZ - class to handle timezone
"""
import time
import re
import datetime

def expandyear(y):
	"""My own bug2000"""
	if y>100: return y
	if y>80: return y+1900
	return y+2000

ZERO = datetime.timedelta(0)
HOUR = datetime.timedelta(hours=1)
class TZ(datetime.tzinfo):
	"""Fixed offset in minutes east from UTC."""
	def __init__(self, name='GMT', offset=None): # default is needed for unpickle to work
		if offset==None:
			if name=='EST':
				offset=-5*60
			elif name=='GMT':
				offset=0
			elif name=='IL': # Israel standard time
				offset=2*60
			elif name=='CET': # European Central Time
				offset=1*60
			else:
				raise Exception('Unknown TZ')
		self.__offset = datetime.timedelta(minutes = offset)
		self.__name = name

	def utcoffset(self, dt):
		return self.__offset

	def tzname(self, dt):
		return self.__name

	def dst(self, dt):
		return ZERO
	
	def __str__(self):
		return self.__name
	
def str2dt(s):
	if s=='NOW':
		t=time.gmtime()
		return datetime.datetime(t[0],t[1],t[2],t[3],t[4],t[5],0,None)		
	if s=='FUTURE':
		return datetime.datetime(2040,12,31,23,59,59,0,None)
	if s=='PAST':
		return datetime.datetime(1980,1,1,0,0,0,0,None)
	fmtzs=[("%d-%b-%y",None), # e.g., 21-Aug-06
		   ("%Y-%m-%d",None),
	       ("%d-%b-%Y",None),
	       ("%d-%b-%Y:%H:%M:%S",None),
	       ("%d%b%y",None), # e.g., 21Aug06
	       ("%y%m%d",None), # This is to protect from %Y%m%d which takes 060111 as year 601.
	       ("%Y%m%d",None), # e.g., 20060821
	       ("%Y%m%d%H%M%S",None),
	       ("%Y%m%d%H%M%SCET",TZ('CET')),
	       ("%Y%m%d%H%M%SCEST",TZ('CET')),
	       ("%Y%m%d%H%M%S[-5:EST]",TZ('EST')),
	       ("%Y%m%d%H%M%S.000[0:GMT]",TZ('GMT')),
	       ("%Y%m%d%H%M%S[0:GMT]",TZ('GMT'))]
	t=None
	for fmt,tz in fmtzs:
		try:
			t=time.strptime(s,fmt)
			break
		except:
			pass
			m = re.match(r"(\d{14})(\.\d{3})?\[([+-]?\d+):([A-Z]{3})]", s)
			if m:  # fixed offset timezone
				try:
						t=time.strptime(m.group(1), "%Y%m%d%H%M%S")  # ignore fractions of second
						tz=TZ(m.group(4), int(m.group(3)) * 60)
				except ValueError:
					pass

	if not t:
		raise Exception('Unknown date format '+s)
	if (t[0]<1980 or t[0]>=2041): raise Exception("Illegal date") # sanity check on year, this match definition of DT('PAST') and DT('FUTURE')
	return datetime.datetime(t[0],t[1],t[2],t[3],t[4],t[5],0,tz)


def dt2str8(dt):
	s=time.strftime('%y%m%d',dt.timetuple()[:3])
	return s

def dt2str(dt):
	"""This is the same format used by fmanage, dont change it"""
	return time.strftime('%d-%b-%Y',dt.timetuple())
def dt2pl(dt):
	"""Convert datetime to a SWI-prolog term"""
	t=dt.timetuple()
	s='date(%d,%d,%d,%d,%d,%d,'%tuple(t[0:6])
	if dt.utcoffset():
		s+=str(-60*dt.utcoffset())+','
	else:
		s+='0,'
	if dt.tzname():
		s+="'"+dt.tzname()+"'"+',' # quote tzname so it will always be an atom in Prolog even if it's capitalized
	else:
		s+='-,'
	if t[8]!=-1:
		s+=str(t[8])
	else:
		s+='-'
	s+=')'
	return s

def dtsub(dt1,dt2):
	"""Compute difference in days between two DT objects"""
	td=dt1-dt2
	return td.days+(td.seconds/86400.)

class DT(object):
	"""Ideally this should be a sub-class of datetime.datetime but for some reason unpickle didnt work.
	In addition datetime is an immutable class so I had to use __new__ instead of __init__
	so instead DT is an envelope/proxy/container to datetime
	"""
	def __init__(self,s):
		if isinstance(s,DT):
			self.dt=s.dt
		elif isinstance(s,datetime.datetime):
			self.dt=s
		else:
			self.dt=str2dt(s)
	def __repr__(self): return self.strfull(True)
	def __str__(self): return dt2str(self.dt)
	def __hash__(self):
		"""From Python Reference Manual: ... if it defines __cmp__() or __eq__() but not __hash__(), its instances will not be usable as dictionary keys...."""
		return hash(self.dt)
	def __eq__(self,other):
		"""Catch comparison with None and other classes"""
		if isinstance(other,DT):
			return self.__cmp__(other)==0
		else:
			return False
	def __ne__(self,other):
		return not self.__eq__(other)
	def __cmp__(self,other):
		"""Compare two DTs.
		If one has a TZ and the other does not then, for the sake of comparison,
		we will assume they are both in the same."""
		if self.dt.tzinfo and not other.dt.tzinfo:
			return cmp(datetime.datetime(*self.dt.timetuple()[:6]),other.dt)
		if not self.dt.tzinfo and other.dt.tzinfo:
			return cmp(self.dt,datetime.datetime(*other.dt.timetuple()[:6]))
		return cmp(self.dt,other.dt)
	def pl(self): return dt2pl(self.dt)
	def startofday(self):
		self.dt=self.dtstartofday()
	def startofyear(self):
		self.dt=self.dtstartofyear()
	def endofday(self): 
		self.dt=self.dtendofday()
	def dtstartofday(self):
		t=self.dt.timetuple()
		return datetime.datetime(t[0],t[1],t[2],0,0,0,0,self.dt.tzinfo)
	def dtstartofyear(self):
		t=self.dt.timetuple()
		return datetime.datetime(t[0],1,1,0,0,0,0,self.dt.tzinfo)
	def dtendofday(self):
		t=self.dt.timetuple()
		return datetime.datetime(t[0],t[1],t[2],23,59,59,0,self.dt.tzinfo)
	def strfull(self,tz=False):
		"""20061201232014[-5:EST]"""
		tt=self.dt.timetuple()
		if any(tt[3:6]):
			s=time.strftime('%Y%m%d%H%M%S',tt)
		else:
			s=time.strftime('%Y%m%d',tt)
		if tz:
			stz=self.dt.tzname()
			td=self.dt.utcoffset()
			if td!=None: hr=td.days*24.+td.seconds/3600.
			if stz and td!=None: s+="[%g:%s]"%(hr,stz)
			elif td!=None: s+="[%g]"%hr
		return s
	def str6(self):
		"""061201"""
		return time.strftime('%y%m%d',self.dt.timetuple())
	def __add__(self,days): return DT(self.dt+datetime.timedelta(days))
	def __sub__(self,other):
		"""If other is DT: Compute difference in days between two DT objects, ignore TZ
		If other is int: compute a new DT that is other=x days before self
		"""
		if isinstance(other,int): return DT(self.dt-datetime.timedelta(other))
		if self.dt.tzinfo and not other.dt.tzinfo:
			return dtsub(datetime.datetime(*self.dt.timetuple()[:6]),other.dt)
		if not self.dt.tzinfo and other.dt.tzinfo:
			return dtsub(self.dt,datetime.datetime(*other.dt.timetuple()[:6]))
		return dtsub(self.dt,other.dt)
	def int(self):
		""" Convert to integer keeping number of days since 1/1/1 (Well not exactly but you get the idea)"""
		return self.dt.date().toordinal()
